package info.cs52.bioinfo.hatien;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;

import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

import prefuse.data.Node;
import prefuse.data.Table;
import prefuse.data.Tree;
import prefuse.data.io.DataIOException;
import prefuse.data.io.TreeMLReader.Tokens;
import prefuse.data.parser.DataParseException;
import prefuse.data.parser.DataParser;
import prefuse.data.parser.ParserFactory;

public class InputReader {

	public Input read(String path) throws DataIOException {
		try {
			return read(new FileInputStream(path),new FileInputStream(path));
		} catch (DataIOException e) {
			throw e;
		} catch (IOException e) {
			throw new DataIOException(e);
		}
	}
	
	public Input read(InputStream in,InputStream inS) throws DataIOException {
		try {
			SAXParserFactory factory = SAXParserFactory.newInstance();
			SAXParser saxParser = factory.newSAXParser();
			InputHandler handler = new InputHandler();
			saxParser.parse(in, handler);
			String[] chars = handler.characteristics.toArray(
					new String[handler.characteristics.size()]);
			
			SAXParserFactory factoryS = SAXParserFactory.newInstance();
			SAXParser saxParserS = factoryS.newSAXParser();
			InputHandler handlerS = new InputHandler();
			saxParserS.parse(inS, handlerS);
			
			return new Input(chars, handler.scores, handler.getTree(), handlerS.getTree());
		} catch (Exception e) {
			throw new DataIOException(e);
		}
	}

    private ParserFactory m_pf = ParserFactory.getDefaultFactory();

    /**
     * A SAX Parser for TreeML data files.
     */
    public class InputHandler extends DefaultHandler implements Tokens {
        
        private Table m_nodes = null;
        private Tree m_tree = null;
        
        private Node m_activeNode = null;
        private boolean m_inSchema = true;

		private List<String> characteristics = new LinkedList<String>();
		private int[][] scores;
		private String state;
		private StringBuffer sb = new StringBuffer();
        
        public void startDocument() {
            m_tree = new Tree();
            m_nodes = m_tree.getNodeTable();
        }
        
        private void schemaCheck() {
            if ( m_inSchema ) {
                m_inSchema = false;
            }
        }
        
        public void endElement(String namespaceURI, String localName, String qName) {
            if ( qName.equals(BRANCH) || qName.equals(LEAF) ) {
                m_activeNode = m_activeNode.getParent();
            } else if ("scores".equals(qName)) {
            	parseScoreMatrix(sb.toString());
            } else if ("characteristics".equals(qName)) {
				if (scores == null) {
					characteristics.add(sb.toString().trim());
				} else {
					System.err.println("Characteristics must be specified fully before score matrix.");
				}
            }
        }
        
        public void startElement(String namespaceURI, String localName,
                                 String qName, Attributes atts) {           
			state = qName;
			if ("characteristics".equals(state) || "scores".equals(state)) {
				sb.delete(0, sb.length());
			}
            if ( qName.equals(DECL) ) {
                if ( !m_inSchema ) {
                    throw new RuntimeException("All declarations must be done "
                            + "before nodes begin");
                }
                String name = atts.getValue(NAME);
                String type = atts.getValue(TYPE);
                Class t = parseType(type);
                m_nodes.addColumn(name, t);
            }
            else if ( qName.equals(BRANCH) || qName.equals(LEAF) ) {
                schemaCheck();
                
                // parse a node element
                Node n;
                if ( m_activeNode == null ) {
                    n = m_tree.addRoot();
                } else {
                    n = m_tree.addChild(m_activeNode);
                }
                m_activeNode = n;
            }
            else if ( qName.equals(ATTR) ) {
                // parse an attribute
                parseAttribute(atts);
            }
        }
        
        protected void parseAttribute(Attributes atts) {
            String alName, name = null, value = null;
            for ( int i = 0; i < atts.getLength(); i++ ) {
                alName = atts.getQName(i);
                if ( alName.equals(NAME) ) {
                    name = atts.getValue(i);
                } else if ( alName.equals(VALUE) ) {
                    value = atts.getValue(i);
                }
            }
            if ( name == null || value == null ) {
                System.err.println("Attribute under-specified");
                return;
            }

            try {
                Object val = parse(value, m_nodes.getColumnType(name));
                m_activeNode.set(name, val);
            } catch ( Exception e ) {
                throw new RuntimeException(e);
            }
        }
        
        protected Object parse(String s, Class type)
            throws DataParseException
        {
            DataParser dp = m_pf.getParser(type);
            return dp.parse(s);
        }
        
        protected Class parseType(String type) {
            type = Character.toUpperCase(type.charAt(0)) +
                   type.substring(1).toLowerCase();
            if ( type.equals(INT) || type.equals(INTEGER) ) {
                return int.class;
            } else if ( type.equals(LONG) ) {
                return long.class;
            } else if ( type.equals(FLOAT) ) {
                return float.class;
            } else if ( type.equals(DOUBLE) || type.equals(REAL)) {
                return double.class;
            } else if ( type.equals(BOOLEAN) ) {
                return boolean.class;
            } else if ( type.equals(STRING) ) {
                return String.class;
            } else if ( type.equals(DATE) ) {
                return Date.class;
            } else {
                throw new RuntimeException("Unrecognized data type: "+type);
            }
        }
        
        public Tree getTree() {
            return m_tree;
        }

        @Override
		public void characters(char[] ch, int start, int length)
				throws SAXException {
			if ("characteristics".equals(state) || "scores".equals(state)) {
				sb.append(ch, start, length);
			}
		}
        
		private void parseScoreMatrix(String string) {
			int n = characteristics.size();
			scores = new int[n][n];
			String[] lines = string.trim().split("\r?\n");
			for (int i = 0; i < n; i++) {
				scores[i] = new int[n];
				String[] cells = lines[i].trim().split("\\s+");
				for (int j = 0; j < n; j++) {
					scores[i][j] = Integer.parseInt(cells[j]);
				}
			}
		}
        
    } 
    
}
